package org.hackystat.ui.projectviewer.gwt.client.panels;

import java.util.Map;
import java.util.Map.Entry;

import org.hackystat.ui.projectviewer.gwt.client.common.MainPanelWidget;
import org.hackystat.ui.projectviewer.gwt.client.common.Session;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectData;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectSummaryData;
import org.hackystat.ui.projectviewer.gwt.client.dataprovider.DataProviderMediator;
import org.hackystat.ui.projectviewer.gwt.client.widgets.PanelTitleWidget;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.ClickListenerCollection;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SourcesClickEvents;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Displays project summary and allows sensor data download.
 *
 * @author Pavel Senin.
 *
 */
public class ProjectDataPanel extends MainPanelWidget implements ClickListener, SourcesClickEvents {

  private final ClickListenerCollection clickListeners = new ClickListenerCollection();

  /* the outer and inner frames */
  private final VerticalPanel outer = new VerticalPanel();
  private final VerticalPanel inner = new VerticalPanel();

  /* the project top-level info */
  private final PanelTitleWidget projectTitle = new PanelTitleWidget();
  private final HTML projectInfoHTML = new HTML();

  /* the project data */
  private final PanelTitleWidget dataTitle = new PanelTitleWidget();
  private final FlexTable dataGrid = new FlexTable();

  /* the project data chart, just for fun, why not ??? */
  private final HorizontalPanel dataChartpanel = new HorizontalPanel();

  private String currPrjName;
  private ProjectData currPrjData;

  private String sdtType;

  // needed for time measurements
  private long startTimeMillis;

  /**
   * Creates an instance and reports a handler.
   *
   * @return The prjectData panel instance.
   */
  public static MPHandler init() {
    return new MPHandler("projectData") {
      public MainPanelWidget createInstance() {
        return new ProjectDataPanel();
      }
    };
  }

  /**
   * Instantiates the panel.
   *
   */
  public ProjectDataPanel() {

    // making up panels and wait image
    outer.setStyleName("infoPanel");
    outer.setVerticalAlignment(VerticalPanel.ALIGN_TOP);
    outer.add(inner);
    inner.setWidth("100%");

    // laying down all panels and tables
    projectTitle.setSingleTitle("Info panel");
    dataTitle.setSingleTitle("Project data summary panel");
    dataChartpanel.setHorizontalAlignment(HorizontalPanel.ALIGN_CENTER);

    inner.add(projectTitle);
    inner.add(projectInfoHTML);
    inner.add(dataTitle);
    inner.add(dataGrid);
    inner.add(dataChartpanel);

    initWidget(outer);
  }

  /**
   * Set the info for the project specified.
   *
   * @param projectId The project name.
   */
  public void setProject(final String projectId) {
    if (DataProviderMediator.getInstance().lock()) {
      // do a little panel cleanup first
      projectTitle.setWait(true);
      projectTitle.setSubTitle("getting data ...");
      projectInfoHTML.setHTML("");
      dataTitle.setWait(true);
      projectTitle.setSubTitle("getting data ...");
      for (int i = dataGrid.getRowCount() - 1; i >= 0; i--) {
        dataGrid.removeRow(i);
      }
      if (dataChartpanel.getWidgetCount() > 0) {
        dataChartpanel.remove(0);
      }
      // Setup the callback from the service.
      AsyncCallback<ProjectData> callback = new AsyncCallback<ProjectData>() {
        // If the call was successful, we will get back the result of authorization.
        public void onSuccess(ProjectData s) {
          DataProviderMediator.getInstance().unlock();
          if (Log.isDebugEnabled()) {
            long endTimeMillis = System.currentTimeMillis();
            float durationSeconds = (endTimeMillis - startTimeMillis) / 1000F;
            Log.debug("PDP-Pane: unlocked. success in : " + durationSeconds + " seconds");
          }
          currPrjData = s;
          currPrjName = s.getName();
          projectTitle.setWait(false);
          projectTitle.setSingleTitle(s.getName());
          projectInfoHTML.setHTML(s.toHTML());
          // get last sensorData
          setSummaries();
        }

        // Something went wrong with the call. Handle the issue how you'd like.
        public void onFailure(Throwable ex) {
          DataProviderMediator.getInstance().unlock();
          if (Log.isDebugEnabled()) {
            long endTimeMillis = System.currentTimeMillis();
            float durationSeconds = (endTimeMillis - startTimeMillis) / 1000F;
            Log.debug("PDP-Pane: unlocked. failure in : " + durationSeconds + " seconds");
          }
          Window.alert("Unknown exception happend during RPC call. \n Please provide feedback "
              + "to hackystat-dev@googlegroups.com. \n Error: " + ex.getMessage());
        }
      };
      if (Log.isDebugEnabled()) {
        startTimeMillis = System.currentTimeMillis();
        Log.debug("PDP-Pane: RPC locked. getting project info for " + projectId);
      }
      DataProviderMediator.getInstance().getProjectData(projectId, callback);
    }
    else {
      Window.alert("RPC is locked, unable to proceed with communications. \n Please provide "
          + "feedback to hackystat-dev@googlegroups.com.");
    }
  }

  /**
   * Sets summaries and links to download the data.
   */
  private void setSummaries() {
    if (DataProviderMediator.getInstance().lock()) {
      // Setup the callback from the service.
      AsyncCallback<ProjectSummaryData> callback = new AsyncCallback<ProjectSummaryData>() {
        // If the call was successful, we will get back the result of authorization.
        public void onSuccess(ProjectSummaryData s) {
          DataProviderMediator.getInstance().unlock();
          if (Log.isDebugEnabled()) {
            long endTimeMillis = System.currentTimeMillis();
            float durationSeconds = (endTimeMillis - startTimeMillis) / 1000F;
            Log.debug("PDP-Pane: unlocked. success in : " + durationSeconds + " seconds");
          }
          ProjectSummaryData psd = s;
          dataTitle.setWait(false);
          dataTitle.setTitle(currPrjData.getName() + " data summary");
          dataTitle.setSubTitle("download section");
          formatSummaryTable(psd);
        }

        // Something went wrong with the call. Handle the issue how you'd like.
        public void onFailure(Throwable ex) {
          DataProviderMediator.getInstance().unlock();
          if (Log.isDebugEnabled()) {
            long endTimeMillis = System.currentTimeMillis();
            float durationSeconds = (endTimeMillis - startTimeMillis) / 1000F;
            Log.debug("PDP-Pane: unlocked. failure in : " + durationSeconds + " seconds");
          }
          Window.alert("Unknown exception happend during RPC call. \n Please provide feedback "
              + "to hackystat-dev@googlegroups.com. \n Error: " + ex.getMessage());
        }
      };
      if (Log.isDebugEnabled()) {
        startTimeMillis = System.currentTimeMillis();
        Log.debug("PDP-Pane: RPC locked. getting data summaries for " + currPrjName);
      }
      DataProviderMediator.getInstance().getProjectSummaryData(currPrjName, callback);
    }
    else {
      Window.alert("RPC is locked, unable to proceed with communications. \n Please provide "
          + "feedback to hackystat-dev@googlegroups.com.");
    }

  }

  /**
   * Formats project summary data into flex table.
   *
   * @param psd The project summary data.
   */
  private void formatSummaryTable(ProjectSummaryData psd) {
    DateTimeFormat dtf = DateTimeFormat.getFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    dataGrid.clear();
    dataGrid.setText(0, 0, "Total data entries");
    dataGrid.setText(0, 1, psd.getDataInstances().toString());
    Map<String, String> dSum = psd.getDataSummary();
    Integer[] values = new Integer[dSum.size()];
    String[] caps = new String[dSum.size()];
    Session session = Session.getInstance();
    int row = 1;
    for (Entry<String, String> entry : dSum.entrySet()) {
      String cap = entry.getKey();
      dataGrid.setText(row, 0, cap);
      caps[row - 1] = cap;
      String value = entry.getValue();
      dataGrid.setText(row, 1, value);
      values[row - 1] = Integer.valueOf(value);
      // Button downloadDataButton = new Button("download");
      // downloadDataButton.setTitle(entry.getKey());
      // downloadDataButton.addClickListener(this);
      // dataGrid.setWidget(row, 2, downloadDataButton);
      HTML link = new HTML("<a href=\"/viewertest/fetchfileservlet?project=" + this.currPrjName
          + "&owner=" + this.currPrjData.getOwner() + "&host=" + session.getTelemetryHost()
          + "&sdt=" + entry.getKey() + "&user=" + session.getUser() + "&password="
          + session.getPassword() + "&startDate=" + dtf.format(currPrjData.getStartTime())
          + "&endDate=" + dtf.format(currPrjData.getEndTime()) + "\"target=\"_blank\">download "
          + entry.getKey() + " data </a>");
      dataGrid.setWidget(row, 2, link);
      row++;
    }
    if (Log.isDebugEnabled()) {
      Log.debug("PDP-Pane: constructed data retrieval links using:");
      Log.debug("PDP-Pane: project Start: " + dtf.format(currPrjData.getStartTime()) + ", end: "
          + dtf.format(currPrjData.getEndTime()));
    }

    // now, let's make a pie chart
    if (caps.length > 0) {
      StringBuffer sb = new StringBuffer(1000);
      // standard url
      sb.append("http://chart.apis.google.com/chart?");
      // chart type
      sb.append("cht=p");
      // chart size
      sb.append("&chs=330x200");
      // data
      sb.append("&chd=" + encodeData(values));
      // captions
      sb.append("&chl=" + caps[0]);
      for (int i = 1; i < caps.length; i++) {
        sb.append("|" + caps[i]);
      }
      if (Log.isDebugEnabled()) {
        Log.debug("Calling url: " + sb.toString());
      }
      dataChartpanel.add(new Image(sb.toString()));
    }

  }

  /**
   * Formats data to the google-cart API format.
   *
   * @param data The chart data.
   * @return Google-Chart API formatted data string.
   */
  public String encodeData(Integer[] data) {
    String simpleEncoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    StringBuffer response = new StringBuffer(500);
    response.append("s:");
    // get maxData
    int maxValue = Integer.MIN_VALUE;
    for (int i = 0; i < data.length; i++) {
      if (data[i] > maxValue) {
        maxValue = data[i];
      }
    }
    // encode
    for (int i = 0; i < data.length; i++) {
      int currentValue = data[i];
      if (currentValue >= 0) {
        response.append(simpleEncoding.charAt(Math.round((simpleEncoding.length() - 1)
            * currentValue / maxValue)));
      }
      else {
        response.append('_');
      }
    }
    return response.toString();
  }

  /**
   * Catches download buttons clicks.
   *
   * @param sender The click sourcing widget.
   */
  public void onClick(Widget sender) {
    if (sender instanceof Button) {
      this.sdtType = ((Button) sender).getTitle();
      clickListeners.fireClick(this);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void addClickListener(ClickListener listener) {
    this.clickListeners.add(listener);
  }

  /**
   * {@inheritDoc}
   */
  public void removeClickListener(ClickListener listener) {
    this.clickListeners.remove(listener);
  }

  /**
   * Report last fired action.
   *
   * @return The last fired action.
   */
  public String getSDTSelection() {
    return this.sdtType;
  }

  /**
   * Reports current project selection.
   *
   * @return The current project selection.
   */
  public String getProjectSelection() {
    return this.currPrjName;
  }

}
